# frozen_string_literal: true

module QA
  RSpec.describe 'Govern', :runner, :external_api_calls, product_group: :threat_insights do
    describe 'Vulnerability management in a merge request' do
      let(:sast_vuln_count) { 5 }
      let(:dependency_scan_vuln_count) { 4 }
      let(:container_scan_vuln_count) { 8 }
      let(:vuln_name) { "Regular Expression Denial of Service in debug" }
      let(:remediable_vuln_name) { "Authentication bypass via incorrect DOM traversal and canonicalization" }
      let(:security_finding_name) { "CVE-2017-18269 in glibc" }
      let(:create_issue_finding) { "Cipher with no integrity" }
      let(:dismiss_reason) { "Won't fix / Accept risk" }
      let(:activity_name) { "HAS_ISSUE" }

      let!(:project) do
        Resource::Project.fabricate_via_api! do |p|
          p.name = 'project-with-security-findings'
          p.description = 'Project with a branch which contains an MR pipeline with security findings'
          p.initialize_with_readme = true
        end
      end

      let!(:vulnerability_report) do
        Support::Retrier.retry_on_exception(sleep_interval: 2, message: "Retrying vulnerability create graphql api") do
          QA::EE::Resource::VulnerabilityItem.fabricate_via_api! do |vulnerability|
            vulnerability.id = project.id
            vulnerability.severity = :CRITICAL
            vulnerability.name = "Bowling vulnerability"
            vulnerability.description = "Run in and roll your arms and pitch the ball"
          end
        end
      end

      let!(:runner) do
        Resource::ProjectRunner.fabricate! do |runner|
          runner.project = project
          runner.name = "runner-for-#{project.name}"
          runner.tags = ['secure_report']
        end
      end

      let!(:source_repository) do
        Resource::Repository::ProjectPush.fabricate! do |push|
          push.project = project
          push.directory = Pathname.new(EE::Runtime::Path.fixture('secure_premade_reports'))
          push.commit_message = 'Check in premade security reports'
          push.branch_name = 'secure-mr'
        end
      end

      let!(:merge_request) do
        Resource::MergeRequest.fabricate_via_api! do |mr|
          mr.project = project
          mr.source_branch = 'secure-mr'
          mr.target_branch = project.default_branch
          mr.source = source_repository
          mr.target = project.default_branch
          mr.target_new_branch = false
        end
      end

      before do
        Flow::Login.sign_in_unless_signed_in
        project.visit!
        Flow::Pipeline.wait_for_latest_pipeline(status: 'passed')
        merge_request.visit!
      end

      after do
        runner.remove_via_api!
      end

      it 'can dismiss a vulnerability with a reason from mr security widget',
        testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348008' do
        dismiss_reason = "Vulnerability not applicable"

        Page::MergeRequest::Show.perform do |merge_request|
          expect(merge_request).to have_vulnerability_report
          merge_request.dismiss_vulnerability_with_reason(vuln_name, dismiss_reason)

          merge_request.click_vulnerability(vuln_name)

          expect(merge_request).to have_security_finding_dismissed_on_mr_widget(dismiss_reason)

          merge_request.cancel_vulnerability_modal
        end

        merge_and_check_pipeline_success
        verify_vulnerability_dismissed(vulnerability: vuln_name)
      end

      it 'can create an issue from a vulnerability from mr security widget',
        testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348007' do
        Page::MergeRequest::Show.perform do |merge_request|
          expect(merge_request).to have_vulnerability_report
          merge_request.create_vulnerability_issue(vuln_name)
        end

        Page::Project::Issue::Show.perform do |issue|
          expect(issue).to have_title("Investigate vulnerability: #{vuln_name}")
        end
      end

      it 'can create an auto-remediation MR from mr security widget',
        testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348009' do
        Page::MergeRequest::Show.perform do |merge_request|
          expect(merge_request).to have_vulnerability_report
          merge_request.resolve_vulnerability_with_mr(remediable_vuln_name)

          # Context changes as resolve method creates new MR
          expect(merge_request).to have_title(remediable_vuln_name)
        end
      end

      it 'can dismiss a security finding with reason from pipeline security tab',
        :reliable, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/378294' do
        visit_pipeline_security_tab
        EE::Page::Project::Secure::PipelineSecurity.perform do |pipeline_security|
          pipeline_security.dismiss_finding_with_reason(security_finding_name, dismiss_reason)
          pipeline_security.toggle_hide_dismissed_off
          pipeline_security.undo_dismiss_button_present?(security_finding_name)
          pipeline_security.expand_security_finding(security_finding_name)
        end

        Page::MergeRequest::Show.perform do |merge_request|
          expect(merge_request).to have_security_finding_dismissed(dismiss_reason, project.full_path)

          merge_request.cancel_vulnerability_modal
        end

        check_vulnerability_absent_in_vuln_report(name: security_finding_name)
        merge_and_check_pipeline_success
        verify_vulnerability_dismissed(vulnerability: security_finding_name)
      end

      it 'can create an issue from a security finding in pipeline security tab',
        testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/383756',
        quarantine: {
          issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/423897',
          type: :bug
        } do
        visit_pipeline_security_tab
        EE::Page::Project::Secure::PipelineSecurity.perform do |pipeline_security|
          pipeline_security.create_issue(create_issue_finding)
        end

        Page::Project::Issue::Show.perform do |issue|
          expect(issue).to have_title("Investigate vulnerability: #{create_issue_finding}")
        end

        check_vulnerability_absent_in_vuln_report(name: create_issue_finding)
        merge_and_check_pipeline_success
        page.refresh
        EE::Page::Project::Secure::Show.perform do |security_dashboard|
          security_dashboard.filter_by_status(['needs triage'])
          security_dashboard.filter_by_activity(activity_name)

          expect(security_dashboard).to have_status('needs triage', create_issue_finding)
        end

        EE::Page::Project::Secure::SecurityDashboard.perform do |security_dashboard|
          expect(security_dashboard).to have_issue_created_icon(create_issue_finding)
        end
      end

      def check_vulnerability_absent_in_vuln_report(name:)
        Page::Project::Menu.perform(&:go_to_vulnerability_report)

        EE::Page::Project::Secure::SecurityDashboard.perform do |security_dashboard|
          expect(security_dashboard).not_to have_vulnerability(description: name)
        end
      end

      def latest_pipeline_status
        create(:pipeline, project: project, id: project.pipelines.first[:id]).status
      end

      def merge_and_check_pipeline_success
        merge_request.merge_via_api!

        expect { project.pipelines.size }
          .to eventually_eq(2)
                .within(max_duration: 60),
            "There are currently #{project.pipelines.size} pipelines in the project instead of 2"

        Support::Waiter.wait_until(sleep_interval: 3) do
          latest_pipeline_status == 'success'
        end
      end

      def verify_vulnerability_dismissed(vulnerability:)
        Page::Project::Menu.perform(&:go_to_vulnerability_report)
        dismissed_url = "#{page.current_url}/?state=DISMISSED"
        visit(dismissed_url)

        EE::Page::Project::Secure::Show.perform do |security_dashboard|
          expect(security_dashboard).to have_status('dismissed', vulnerability)
        end
      end

      def visit_pipeline_security_tab
        Flow::Pipeline.visit_latest_pipeline
        Page::Project::Pipeline::Show.perform(&:click_on_security)
      end
    end
  end
end
